#include "Client.h"

Client::Command* commands[] = {
        new Client::Command("quit", "Exit the program", "/q or /quit"),
        new Client::Command("help", "Displays this help file.", "/h or /help"),
        new Client::Command("connect", "Connects to a server.", "/c or /connect [ip or domain]"),
        new Client::Command("disconnect", "Disconnects from current server.", "/d or /disconnect"),
        new Client::Command("list", "Lists the users on server.", "/l or /list"),
        new Client::Command("nick", "Set a nick name on the server.", "/nick")
};

Client::Client(void)
{
        //      filePort = FILE_PORT;
        cursor = 0;
        scrollPos = 0;
        characters = 0;
        message[0] = '\0';
        host = NULL;
        isRunning = true;

        // Empty switcher set
        FD_ZERO(&switcher);
        // Watch STDIN for input.
        FD_SET(fileno(stdin), &switcher);

        max_fd = fileno(stdin);
}

Client::~Client( void )
{
        system("PAUSE");
        disconnect();
        // Teardown client
}

#ifdef __WIN32__
void Client::run(void)
{
        DWORD dwWait = 0;
        Dispatch *packet = NULL;
        unsigned int num_handles = 1;
        int handle = 0;

        HANDLE handles[] = {
                GetStdHandle(STD_INPUT_HANDLE),
                WSA_INVALID_EVENT
        };

        while (isRunning)
        {
                // Check to see if we have a host.
                // If we do, create an event, and
                // configure it with listening to
                // the socket for a 'ready to read'
                if (host != NULL)
                {
                        num_handles = 2;
                        handles[1] = WSACreateEvent();
                        WSAEventSelect((SOCKET)textSock, handles[1], FD_READ);

                } else num_handles = 1;

                // Wait on the handles, (STDIN if no connection, STDIN and socket if there is)
                dwWait = WaitForMultipleObjects(num_handles,    //number of event objects
                                                                                handles,        // array of event objects
                                                                                FALSE,                  // does not wait for all
                                                                                INFINITY);              // Waits forever

                // Which handle is ready?
                handle = dwWait - WAIT_OBJECT_0;

                if (handle < 0 || handle > (num_handles - 1)) //NUM_HANDLES = 2
                {
                        addLine(0, "Index out of range.");
                        printf("Index out of range.\n");

                        // Event was the socket having something to read.
                } else if (handle == 1)
                {
                        // New dispatch
                        packet = new Dispatch();

                        // Read from the socket
                        if ( packet->receive(textSock) == -1 )
                        {
                                addLine(0, "Error recving from socket stream.");

                        } else if( packet->getBytes() == 0 )
                        {
                                addLine(0, "Server disconnected");
                                disconnect();

                        } else processDispatch(packet);

                        delete packet;

                        // Otherwise, it was an input event.
                } else catchInput();

                updateWindow();
        }
}
#else
void Client::run(void)
{
        fd_set handles;
        Dispatch *packet = NULL;

        // Clear out entire set
        FD_ZERO(&handles);

        while (isRunning)
        {
                FD_ZERO(&switcher);
                FD_SET(fileno(stdin), &switcher);

                if(host != NULL)
                {
                        FD_SET(textSock, &switcher);
                        select(textSock+1, &switcher, NULL, NULL, 0);
                } else {
                        select(fileno(stdin)+1, &switcher, NULL, NULL, 0);
                }

                if(FD_ISSET(fileno(stdin), &switcher))
                {
                        catchInput();
                } else if(host != NULL)
                {
                        if(packet->receive(textSock) == -1)
                        {
                                addLine(0, "Error recving from socket stream.");
                        } else if(packet->getBytes() == 0)
                        {
                                addLine(0, "Server disconnected");
                                disconnect();
                        } else {
                                processDispatch(packet);
                        }
                }
                updateWindow();
        }

}
#endif

bool Client::connectTo( const std::string ip)
{
        disconnect();
        addLine(0, "Connecting...");
        if (host != gethostbyname(ip.c_str())) {
            addLine(0, "Error Connecting, check Host/IP.....");
            disconnect();
            return false;
        }
        else {
            if((textSock = socket(PF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) {
                addLine(0, "Invalid Socket....");
                disconnect();
                return false;
            }
            connection.sin_family = AF_INET;
            connection.port = htons(textPort);
            connection.sin_addr.s_addr = inet_addr( ip.c_str() );
            host = 1;
        }
        // STEP: Make sure disconnected before connecting ### check
        // STEP: Check ip by using gethostbyame to see if connting to a valid host  ### check
        // STEP: check to see if textSock is a valid socket ### check

        addLine(0, "Opening a connection...");

        // STEP: Create connection to host via information
        // from gethostbyname() using sockaddr_in etc

                // Add socket to master set
                FD_SET(textSock, &switcher);

                // Set the maximum file descriptor
                max_fd = max_fd > textSock ? max_fd : textSock;

                Dispatch user = Dispatch(CLIENT_REQUEST_NAME, username);
                packet.release(textSock);
                // STEP: Notify server upon successful connection of client name.
                // HINT: Make request to server to use `username'

                addLine(0, "Client initialization successfull.");

        return true;
}

void Client::disconnect()
{
        if(host == null) {
        return;
        }
        closesocket(textSock)
        Dispatch gone Dispatch(CLIENT_DISCONNECT, username);
        gone.release(textSock);
        // STEP: Make sure your connected ###check
        // STEP: Tell the server you are disconnecting ### check
        // STEP: Close the socket ### check

        host = NULL;
        addLine(0, "Connection Closed.");

        // Remove socket from master set.
        FD_CLR(textSock, &switcher);
}

bool Client::validIP( const std::string ip)
{
        struct sockaddr_in sa;
        int result = inet_pton(AF_INET, ip.c_str(), &(sa.sin_addr));
        return result != 0;
        // STEP: Make sure that the IP address is in a valid format  ### check
}

void Client::processDispatch (Dispatch *dispatch)
{
        switch(packet->getFlag()) {
            case CLIENT_DISCONNECT:
                addLine(0, std::string(packet->getData()) + " is gone from the chat.");
                break;
    
            default:
                addLine(0, std::string("Unable to place data") + packet->getData());
                break;

        }
        // STEP: Process each Flag of dispatch ### check
        // HINT: Use a switch statement for each DispatchFlag ### check

        // STEP: Keep track of corrupt or misunderstood packets ### maybe check
        // HINT: use CORRUPT and default: cases to handle that. ### maybe check

}

void Client::processCommands(char* message)
{
    char str[16];
    char i[32];
    sscanf(message, "%s %*s", str, i);
    // TODO: Create User commands for the client ### check
    switch (message) {
        case if(strcmp(str, "info") == 0) info(i);
            break;
        case if(strcmp(str, "help") == 0) help(i);
            break;
        case if(strcmp(str, "quit") == 0) quit(i);
            break;
        case if(strcmp(str, "nick") == 0) nick(i);
            break;
        case if(strcmp(str, "connect") == 0) connect(i);
            break;
        case if(strcmp(str, "disconnect") == 0) disconnect(i);
            break;
        case if(strcmp(str, "list") == 0) requestUserList();

    }

}

void Client::requestUserList( void )
{
        // TODO: get a list of the users from the server
        // HINT: Use LIST_USERS as dispatch flag
}

void Client::info(const char *args)
{
        if(host != NULL) {
        addLine(0, std::string(username) + ": you are connected to the chat client");
        }
        else {
        addLine(0, std::string(username) + ": you are not connected to the chat client");
        }
        // TODO: Display to user information about connection and any other relevant information.
}

void Client::help(const char *args)
{
        int cmds = (sizeof(commands))/sizeof(Client::Command*);

        if (!strcmp(args, ""))
        {
                if (1 == 0)
                        addLine(0, "No help file found, help has been disabled.");

                addLine(0, "\t=======================================");
                addLine(0, "\tHelp! Below are the available commands.");
                addLine(0, "\tType `/help [command]' for more information.");
                addLine(0, "\t=======================================");

                // For converting int to string.
                std::stringstream out;
                for (unsigned int i = 0; i < cmds; i++)
                {
                        // Empty the converter
                        out.str("");
                        out << i+1;
                        addLine(0, std::string("\t" + out.str() + ".\t`" + commands[i]->cmd) + "'\t\t" + commands[i]->dsc);
                }

                addLine(0, "\t=======================================");
                addLine(0, "\tEnd help. I hope you're no longer lost!");
                addLine(0, "\t=======================================");

        } else
        {
                for (int j = 0; j < cmds; j++)
                {
                        if (std::string(args) == commands[j]->cmd)
                        {
                                addLine(0, std::string("Command: ") + commands[j]->cmd);
                                addLine(0, commands[j]->dsc);
                                addLine(0, std::string("Use: ") + commands[j]->use);
                                return;
                        }
                }
                addLine(0, std::string("No help avalible for ") + args);
        }
}

void Client::quit(const char* quitMessage)
{
        disconnect();
        isRunning = false;
}

void Client::nick(const char* nick)
{
        // If the client has a connection open, we'll
        // want to tell the server of the nick change
        // Then the server needs to send back a message
        // Saying: old_nick is now known as new_nick
        username = nick;

        if (host != NULL)
        {
                Dispatch packet = Dispatch(CLIENT_REQUEST_NAME, username);
                packet.release(textSock);
                addLine(0, std::string("Username changed to ") + username);
        } else {
                addLine(0, std::string("Username set to ") + username);
        }
}

void Client::readLine (char* line, FILE* file)
{
        char* temp = line;
        int val = -1;
        while (val != '\r' && val != '\n')
        {
                val = getc(file);
                if (val == EOF)
                {
                        if (feof(file)) break;
                        else                    temp = NULL;
                }
                *temp++ = val;
        }
        *temp = '\0';
}
